using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using DirectShowLib;
using System.Threading;
using System.Runtime.InteropServices;
using System.Diagnostics;
using DirectShowLib.Dvd;
using System.IO;
using DirectShowLib.Utils;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices.ComTypes;

namespace Lizk.DShowPlayer
{
    public class DVDPlayer: Player, IDisposable
    {
        #region DirectShow Properties
        private IVMRSurfaceAllocator9 allocator;
        private IDvdGraphBuilder dvdGraphBuilder;
        private IDvdControl2 dvdControl;
        private IDvdInfo2 dvdInfo;
        private IAMLine21Decoder line21;
        private const int WM_DVD_EVENT = 0x00008002;
        #endregion

        private bool subtitlesDisabled = false;
        private int selectedButton, buttons;
        private DvdDomain domain;
        private TimeSpan position;
        private List<DvdCommand> commands = new List<DvdCommand>();
        public const int E_DVDOperationInhibited = unchecked((int)0x80040276);
        public static string BookMarkFile { get; set; }
        
        public string File { get; set; }
        public int CurrentTitle { get; set; }
        public int CurrentChapter { get; set; }
        public int Titles { get; set; }
        public int Chapters { get; set; }
        public int CurrentAngle { get; set; }
        public int Angles { get; set; }
        public event EventHandler TitleChanged;
        public event EventHandler TrackChanged;
        public event EventHandler BeforeSendAsync;
        public event EventHandler AfterSendAsync;
        public event EventHandler DiskError;
        public event EventHandler DiskEjected;

        public DVDPlayer(string file)
        {
            this.File = file;
        }

        public override void Load(Control targetControl)
        {
            throw new NotImplementedException();
        }

        public override void Load(Form owner, IVMRSurfaceAllocator9 allocator)
        {
            this.mainForm = owner;
            this.allocator = allocator;

            // Create Graph
            dvdGraphBuilder = (IDvdGraphBuilder)new DvdGraphBuilder();

            // Enable VMR9
            object tempFilter;
            int hr = dvdGraphBuilder.GetDvdInterface(typeof(IVMRFilterConfig9).GUID, out tempFilter);
            DsError.ThrowExceptionForHR(hr);

            // Store a reference for later disposal
            vmr9 = (IBaseFilter)tempFilter;

            // Get filterconfig and enable Renderless mode.
            IVMRFilterConfig9 vmrConfig = (IVMRFilterConfig9)tempFilter;
            hr = vmrConfig.SetRenderingMode(VMR9Mode.Renderless);
            DsError.ThrowExceptionForHR(hr);

            // Create the Allocator / Presenter object
            IVMRSurfaceAllocatorNotify9 vmrSurfAllocNotify = (IVMRSurfaceAllocatorNotify9)tempFilter;

            // Notify the VMR9 filter about our allocator
            hr = vmrSurfAllocNotify.AdviseSurfaceAllocator(IntPtr.Zero, allocator);
            DsError.ThrowExceptionForHR(hr);

            // Notify our allocator about the VMR9 filter
            hr = allocator.AdviseNotify(vmrSurfAllocNotify);
            DsError.ThrowExceptionForHR(hr);

            // Set options
            IVMRMixerControl9 vmrMixerControl = (IVMRMixerControl9)tempFilter;
            hr = vmrMixerControl.SetMixingPrefs(VMR9MixerPrefs.RenderTargetYUV | VMR9MixerPrefs.NoDecimation | VMR9MixerPrefs.BiLinearFiltering);
            DsError.ThrowExceptionForHR(hr);

            // Attempt to render dvd
            AMDvdRenderStatus status;
            hr = dvdGraphBuilder.RenderDvdVideoVolume(Path.GetDirectoryName(File), AMDvdGraphFlags.HWDecPrefer, out status);
            DsError.ThrowExceptionForHR(hr);

            IGraphBuilder gb;
            hr = dvdGraphBuilder.GetFiltergraph(out gb);
            DsError.ThrowExceptionForHR(hr);

            graphBuilder = (IFilterGraph2)gb;

            this.mediaControl = (IMediaControl)this.graphBuilder;
            this.mediaEventEx = (IMediaEventEx)this.graphBuilder;
            this.mediaSeeking = (IMediaSeeking)this.graphBuilder;

            // Have the graph signal event via window callbacks for performance
            hr = mediaEventEx.SetNotifyWindow(mainForm.Handle, WM_DVD_EVENT, new IntPtr(this.GetHashCode())); // send notifications but this fileplayer should only read notifications that it owns.
            DsError.ThrowExceptionForHR(hr);


            dvdGraphBuilder.GetDvdInterface(typeof(IAMLine21Decoder).GUID, out tempFilter);
            line21 = (IAMLine21Decoder)tempFilter;

            if (line21 != null)
            {
                // Disable Closed-Caption
                hr = line21.SetCurrentService(AMLine21CCService.None);
                DsError.ThrowExceptionForHR(hr);
            }
            dvdGraphBuilder.GetDvdInterface(typeof(IDvdControl2).GUID, out tempFilter);
            dvdControl = (IDvdControl2)tempFilter;

            // use new HMSF timecode format
            hr = dvdControl.SetOption(DvdOptionFlag.HMSFTimeCodeEvents, true); 
            DsError.ThrowExceptionForHR(hr);

            // Do not reset the dvd to FirstPlay on stop
            hr = dvdControl.SetOption(DvdOptionFlag.ResetOnStop, false);
            DsError.ThrowExceptionForHR(hr);

            dvdGraphBuilder.GetDvdInterface(typeof(IDvdInfo2).GUID, out tempFilter);
            dvdInfo = (IDvdInfo2)tempFilter;

            int volumes, volume, titles;
            DvdDiscSide side;
            hr = dvdInfo.GetDVDVolumeInfo(out volumes, out volume, out side, out titles);
            DsError.ThrowExceptionForHR(hr);

            mediaControl.Run();
            ChangeState(PlayerState.Playing);
        }
        
        public override void EnableAudioTrack(int id)
        {
            if (!CanUseCommands())
                return;

            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null);
            IDvdCmd icmd;
            int hr;
            hr = dvdControl.SelectAudioStream(id, DvdCmdFlags.Flush | DvdCmdFlags.SendEvents, out icmd);
            if (hr != E_DVDOperationInhibited)
                DsError.ThrowExceptionForHR(hr);
            if (AfterSendAsync != null)
                AfterSendAsync(this, null);
        }

        public override void EnableSubtitle(int id)
        {
            if (!CanUseCommands() || state == PlayerState.Paused)
                return;
            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null);
            IDvdCmd icmd;
            int hr;
            if (id == -1 && subtitle != -1)
            {
                hr = dvdControl.SetSubpictureState(false, DvdCmdFlags.Flush | DvdCmdFlags.SendEvents, out icmd);
                if (hr != E_DVDOperationInhibited)
                {
                    DsError.ThrowExceptionForHR(hr);
                    commands.Add(new DvdCommand() { Type = Command.TrackChange, Command = icmd });
                    subtitle = -1;
                    subtitlesDisabled = true;
                }
                if (AfterSendAsync != null)
                    AfterSendAsync(this, null);
                return;
            }
            else if (id != -1)
            {
                hr = dvdControl.SetSubpictureState(true, DvdCmdFlags.Flush | DvdCmdFlags.SendEvents, out icmd);
                if (hr != E_DVDOperationInhibited)
                {
                    DsError.ThrowExceptionForHR(hr);
                    commands.Add(new DvdCommand() { Type = Command.TrackChange, Command = icmd });
                    subtitlesDisabled = false;
                }
            }
            hr = dvdControl.SelectSubpictureStream(id, DvdCmdFlags.Flush | DvdCmdFlags.SendEvents, out icmd);
            if (hr != E_DVDOperationInhibited)
            {
                DsError.ThrowExceptionForHR(hr);
                commands.Add(new DvdCommand() { Type = Command.TrackChange, Command = icmd });
            }
                if (AfterSendAsync != null)
                AfterSendAsync(this, null);
        }


        protected override TimeSpan GetPosition()
        {
            return position;
        }

        public override void Play()
        {


            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null);

            int hr = dvdControl.Pause(false);
            if (hr != E_DVDOperationInhibited)
            {
                DsError.ThrowExceptionForHR(hr);

                hr = mediaControl.Run();
                DsError.ThrowExceptionForHR(hr);
            }
            if (AfterSendAsync != null)
                AfterSendAsync(this, null);
            if (hr != E_DVDOperationInhibited)

                ChangeState(PlayerState.Playing);
            /*int hr = mediaControl.Run();
            DsError.ThrowExceptionForHR(hr);
            */
        }

        protected override bool Message(ref Message m)
        {
            if(mediaEventEx != null)
            switch (m.Msg)
            {
                case WM_DVD_EVENT:
                    {
                        IntPtr p1, p2;
                        int hr = 0;
                        EventCode code;
                        do
                        {
                            hr = mediaEventEx.GetEvent(out code, out p1, out p2, 0);
                            if (code != 0 && code != EventCode.DvdCurrentHmsfTime)
                            {
                                Debug.Write(code + ": ");
                                Debug.WriteLine("p1: " + p1 + " - p2: " + p2);
                            }
                            if (hr < 0)
                            {
                                break;
                            }

                            switch (code)
                            {
                                case EventCode.Complete:
                                    break;
                                case EventCode.Paused:
                                    ChangeState(PlayerState.Paused);
                                    break;
                                case EventCode.DvdDomainChange:
                                    if (BeforeSendAsync != null)
                                        BeforeSendAsync(this, null);
                                    domain = (DvdDomain)p1;
                                    switch (domain)
                                    {
                                        case DvdDomain.FirstPlay:
                                            ChangeState(PlayerState.Playing);
                                            break;
                                        case DvdDomain.VideoManagerMenu:
                                        case DvdDomain.VideoTitleSetMenu:
                                            ChangeState(PlayerState.Menu);
                                            Subtitles.Clear();
                                            AudioTracks.Clear();
                                            UpdateTrackAmount();
                                            break;
                                        case DvdDomain.Title:
                                            ChangeState(PlayerState.Playing);
                                            dvdInfo.GetCurrentDomain(out domain);
                                            if(domain == DvdDomain.Title)
                                                UpdateTitleInfo(CurrentTitle);
                                            break;
                                        case DvdDomain.Stop:
                                            ChangeState(PlayerState.Paused);
                                            break;
                                    }
                                    if (AfterSendAsync != null)
                                        AfterSendAsync(this, null);
                                    break;
                                case EventCode.DvdTitleChange:
                                    CurrentTitle = p1.ToInt32();
                                    if (BeforeSendAsync != null)
                                        BeforeSendAsync(this, null);
                                    if (domain == DvdDomain.Title)
                                    {
                                        audioTrack = subtitle = -1;
                                        UpdateTitleInfo(CurrentTitle);
                                    }
                                    if (AfterSendAsync != null)
                                        AfterSendAsync(this, null);
                                    if (TrackChanged != null)
                                        TrackChanged(this, EventArgs.Empty);
                                    break;
                                case EventCode.DvdChapterStart:
                                    this.CurrentChapter = p1.ToInt32();
                                    ChangeState(PlayerState.Playing);
                                    if (TrackChanged != null)
                                        TrackChanged(this, EventArgs.Empty);
                                    break;
                                case EventCode.DvdAudioStreamChange:
                                    audioTrack = p1.ToInt32();
                                    if (TrackChanged != null)
                                        TrackChanged(this, EventArgs.Empty);
                                    break;
                                case EventCode.DvdSubPicictureStreamChange:
                                    if (state == PlayerState.Menu)
                                        break;
                                    if (!subtitlesDisabled)
                                        subtitle = p1.ToInt32();
                                    else
                                        subtitle = -1;

                                    if (TrackChanged != null)
                                        TrackChanged(this, EventArgs.Empty);
                                    break;
                                case EventCode.DvdAngleChange:
                                    this.Angles = p1.ToInt32();
                                    this.CurrentAngle = p2.ToInt32();
                                    if (TrackChanged != null)
                                        TrackChanged(this, EventArgs.Empty);
                                    break;
                                case EventCode.DvdButtonChange:
                                    buttons = p1.ToInt32();
                                    selectedButton = p2.ToInt32();
                                    break;
                                case EventCode.DvdValidUopsChange:
                                    break;
                                case EventCode.DvdStillOn:
                                    break;
                                case EventCode.DvdStillOff:
                                    break;
                                case EventCode.DvdCurrentTime:
                                    break;
                                case EventCode.DvdError:
                                    if (DiskError != null)
                                        DiskError(this, null);
                                    break;
                                case EventCode.DvdWarning:
                                    break;
                                case EventCode.DvdChapterAutoStop:
                                    break;
                                case EventCode.DvdNoFpPgc:
                                    break;
                                case EventCode.DvdPlaybackRateChange:
                                    break;
                                case EventCode.DvdParentalLevelChange:
                                    break;
                                case EventCode.DvdPlaybackStopped:
                                    break;
                                case EventCode.DvdAnglesAvailable:
                                    if (BeforeSendAsync != null)
                                        BeforeSendAsync(this, null);
                                    if (p1.ToInt32() == 1)
                                    {
                                        int angles, currentAngle;
                                        dvdInfo.GetCurrentAngle(out angles, out currentAngle);
                                        this.Angles = angles;
                                        this.CurrentAngle = currentAngle;
                                    }
                                    else
                                    {
                                        this.CurrentAngle = this.Angles = 1;
                                    }
                                    if (AfterSendAsync != null)
                                        AfterSendAsync(this, null);
                                    break;
                                case EventCode.DvdPlayPeriodAutoStop:
                                    break;
                                case EventCode.DvdButtonAutoActivated:
                                    break;
                                case EventCode.DvdCmdStart:
                                    break;
                                case EventCode.DvdCmdEnd:
                                    IDvdCmd dvdCommand;
                                    if (BeforeSendAsync != null)
                                        BeforeSendAsync(this, null);
                                    hr = dvdInfo.GetCmdFromEvent(p1, out dvdCommand);
                                    for (int i = 0; i < commands.Count; i++)
                                    {
                                        if (commands[i].Command == dvdCommand)
                                        {
                                            // Handle the command (i need to save more info about the command, so that i can figure out what the command was for)
                                            switch (commands[i].Type)
                                            {
                                                case Command.RootMenu:
                                                    ChangeState(PlayerState.Menu);
                                                    break;
                                                case Command.Seek:
                                                case Command.ChangeChapter:
                                                    DvdPlaybackLocation2 location;
                                                    hr = dvdInfo.GetCurrentLocation(out location);
                                                    DsError.ThrowExceptionForHR(hr);

                                                    CurrentChapter = location.ChapterNum;
                                                    CurrentTitle = location.TitleNum;
                                                    position = new TimeSpan(location.TimeCode.bHours, location.TimeCode.bMinutes, location.TimeCode.bSeconds);

                                                    ChangeState(PlayerState.Playing);
                                                    break;
                                                default:
                                                    break;
                                            }
                                            Marshal.ReleaseComObject(commands[i].Command);
                                            commands.RemoveAt(i--);
                                        }
                                    }
                                    Marshal.ReleaseComObject(dvdCommand);
                                    if (AfterSendAsync != null)
                                        AfterSendAsync(this, null);
                                    break;
                                case EventCode.DvdDiscEjected:
                                    if (DiskEjected != null)
                                        DiskEjected(this, null);
                                    break;
                                case EventCode.DvdDiscInserted:
                                    break;
                                case EventCode.DvdCurrentHmsfTime:
                                    byte[] ati = BitConverter.GetBytes(p1.ToInt32());
                                    position = new TimeSpan(ati[0], ati[1], ati[2]);

                                    break;
                                case EventCode.DvdKaraokeMode:
                                    break;
                            }

                            hr = mediaEventEx.FreeEventParams(code, p1, p2);
                        }
                        while (hr == 0);
                        break;
                    }
            }
            return false;
        }

        public void Seek(long ticks, bool absolute)
        {
            if (!CanUseCommands())
            {
                Play();
            }
            
            if (!CanUseCommands())
                return;

            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null);

            DvdHMSFTimeCode timeCode = new DvdHMSFTimeCode();
            int hr;
            if (!absolute)
                ticks += position.Ticks;

            if (ticks < 0)
                ticks = 0;

            if (ticks >= duration.Ticks)
            {
                ticks = duration.Ticks;
            }
            TimeSpan ts = new TimeSpan(ticks);

            timeCode.bSeconds = (byte)ts.Seconds;
            timeCode.bMinutes = (byte)ts.Minutes;
            timeCode.bHours = (byte)ts.Hours;
            IDvdCmd icmd;

            FilterState state;
            hr = mediaControl.GetState(1000, out state);
            DsError.ThrowExceptionForHR(hr);

            hr = dvdControl.PlayAtTime(timeCode, DvdCmdFlags.Flush | DvdCmdFlags.SendEvents, out icmd);
            if (hr != E_DVDOperationInhibited)
            {
                DsError.ThrowExceptionForHR(hr);
                commands.Add(new DvdCommand() { Type = Command.Seek, Command = icmd });
            }
            if (AfterSendAsync != null)
                AfterSendAsync(this, null);
            /*
            DsError.ThrowExceptionForHR(hr);

            DvdPlaybackLocation2 location;
            hr = dvdInfo.GetCurrentLocation(out location);
            DsError.ThrowExceptionForHR(hr);

            currentChapter = location.ChapterNum;
            currentTitle = location.TitleNum;
            position = new TimeSpan(location.TimeCode.bHours, location.TimeCode.bMinutes, location.TimeCode.bSeconds);


            ChangeState(PlayerState.Playing);
            */
            //            hr = dvdControl.Resume(DvdCmdFlags.Block, out icmd);
            //            DsError.ThrowExceptionForHR(hr);
            //GetProcAmpControl();
        }


        public override void Dispose()
        {
            if (dvdControl != null && mediaControl != null)
            {
                try
                {
                    DsError.ThrowExceptionForHR(mediaControl.Stop());
                }
                catch { }
            }


            if (mediaEventEx != null)
            {
                int hr = mediaEventEx.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
            }

            if (dvdInfo != null)
            {
                Marshal.ReleaseComObject(dvdInfo);
                dvdInfo = null;
            }

            if (dvdControl != null)
            {
                Marshal.ReleaseComObject(dvdControl);
                dvdControl = null;
            }

            if (vmr9 != null)
            {
                Marshal.ReleaseComObject(vmr9);
                vmr9 = null;
            }

            if (line21 != null)
            {
                Marshal.ReleaseComObject(line21);
                line21 = null;
            }

            mediaControl = null;
            mediaSeeking = null;
            mediaPosition = null;
            mediaEventEx = null;

            if (graphBuilder != null)
            {
                Marshal.ReleaseComObject(graphBuilder);
                graphBuilder = null;
            }
            if (dvdGraphBuilder != null)
            {
                Marshal.ReleaseComObject(dvdGraphBuilder);
                dvdGraphBuilder = null;
            }
        }
        
        private void DisposeFilter(IGraphBuilder graphBuilder)
        {
            int hr = 0;
            IEnumFilters enumFilters;

            if (graphBuilder == null)
                throw new ArgumentNullException("graphBuilder");

            hr = graphBuilder.EnumFilters(out enumFilters);
            DsError.ThrowExceptionForHR(hr);

            try
            {
                IPersist[] filters = new IPersist[1];

                while (enumFilters.Next(filters.Length, filters, IntPtr.Zero) == 0)
                {

                    Marshal.ReleaseComObject(filters[0]);
                }
            }
            finally
            {
                Marshal.ReleaseComObject(enumFilters);
            }
        }

        public void MenuMove(DvdRelativeButton button)
        {
            if (dvdControl == null || buttons == 0)
            {
                return;
            }

            int hr = dvdControl.SelectRelativeButton(button);
            if (hr != E_DVDOperationInhibited)
                DsError.ThrowExceptionForHR(hr);
        }

        public void MenuSelect()
        {
            if (dvdControl == null || selectedButton == 0 || buttons == 0)
            {
                return;
            }
            int hr = dvdControl.ActivateButton();
            if (hr != E_DVDOperationInhibited)
                DsError.ThrowExceptionForHR(hr);
        }


        public void SetChapter(int chapter)
        {
            if (!CanUseCommands())
            {
                Play();
            }

            if (!CanUseCommands())
                return;
            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null);
            IDvdCmd icmd;
            int hr = dvdControl.PlayChapter(chapter, DvdCmdFlags.SendEvents | DvdCmdFlags.Flush, out icmd);
            if (hr >= 0)
                commands.Add(new DvdCommand() { Type = Command.ChangeChapter, Command = icmd });
            if (AfterSendAsync != null)
                AfterSendAsync(this, null);
        }

        public void NextChapter()
        {

            if (!CanUseCommands())
            {
                Play();
            }
            if (!CanUseCommands())
                return;
            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null);
            IDvdCmd icmd;
            int hr = dvdControl.PlayNextChapter(DvdCmdFlags.SendEvents | DvdCmdFlags.Flush, out icmd);
            if(hr >= 0)
                commands.Add(new DvdCommand() { Type = Command.ChangeChapter, Command = icmd });
            if (AfterSendAsync != null)
                AfterSendAsync(this, null);
        }

        public void PreviousChapter()
        {
            if (!CanUseCommands())
            {
                Play();
            }
            
            if (!CanUseCommands())
                return;
            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null); 
            IDvdCmd icmd;
            int hr = dvdControl.PlayPrevChapter(DvdCmdFlags.SendEvents | DvdCmdFlags.Flush, out icmd);
            if(hr >= 0)
                commands.Add(new DvdCommand() { Type = Command.ChangeChapter, Command = icmd });
            if (AfterSendAsync != null)
                AfterSendAsync(this, null);
        }

        public void Pause()
        {
            if (commands.Count > 0)
                return;


            int hr;
            hr = dvdControl.Pause(true);
            if (hr != E_DVDOperationInhibited)
            {

                DsError.ThrowExceptionForHR(hr);

                hr = mediaControl.Pause();
                DsError.ThrowExceptionForHR(hr);
                ChangeState(PlayerState.Paused);
            }
                            /*
            FilterState state;
            hr = mediaControl.GetState(5000, out state);
            DsError.ThrowExceptionForHR(hr);

            if (state == FilterState.Paused)
                return;

            hr = mediaControl.Pause();
            DsError.ThrowExceptionForHR(hr);
            if (hr == 1)
            {
                hr = mediaControl.GetState(5000, out state);
                DsError.ThrowExceptionForHR(hr);
            }*/

        }

        private bool CanUseCommands()
        {
            return state != PlayerState.Paused;
        }


        public bool RootMenu()
        {
            if (!CanUseCommands())
                return false;
            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null); 
            IDvdCmd icmd;

            if (dvdControl == null)
            {
                return false;
            }

            int hr = dvdControl.ShowMenu(DvdMenuId.Root, DvdCmdFlags.SendEvents | DvdCmdFlags.Flush, out icmd);
            if (hr != E_DVDOperationInhibited)
            {
                DsError.ThrowExceptionForHR(hr);
                commands.Add(new DvdCommand() { Command = icmd, Type = Command.RootMenu });
            } 
            if (AfterSendAsync != null)
                AfterSendAsync(this, null);
            return hr == 0;
        }

        private class DvdCommand
        {
            internal IDvdCmd Command { get; set; }
            internal Command Type { get; set; }
        }

        private enum Command { RootMenu, Seek, ChangeChapter, TrackChange }

        private void UpdateTitleInfo(int title)
        {
            int hr;
            DvdTitleAttributes attributes = new DvdTitleAttributes();
            DvdMenuAttributes menuAttributes;
            DvdHMSFTimeCode time = new DvdHMSFTimeCode();
            DvdTimeCodeFlags codeflags;
            hr = dvdInfo.GetTotalTitleTime(time, out codeflags);
            DsError.ThrowExceptionForHR(hr);

            duration = new TimeSpan(time.bHours, time.bMinutes, time.bSeconds);

            hr = dvdInfo.GetTitleAttributes(title, out menuAttributes, attributes);
            DsError.ThrowExceptionForHR(hr);
            int chapters;
            hr = dvdInfo.GetNumberOfChapters(title, out chapters);
            DsError.ThrowExceptionForHR(hr);
            this.Chapters = chapters;

            this.audioTracks.Clear();
            this.subtitles.Clear();

            for (int i = 0; i < attributes.ulNumberOfAudioStreams; i++)
            {
                bool enabled;
                dvdInfo.IsAudioStreamEnabled(i, out enabled);
                if (!enabled)
                    continue;
                string audioTrackName;
                try
                {
                    CultureInfo language = new CultureInfo(attributes.AudioAttributes[i].Language);
                    audioTrackName = Regex.Replace(language.EnglishName, @"\(.*\)", "") + "(" +
                                     attributes.AudioAttributes[i].bNumberOfChannels + "ch)";
                }
                catch
                {
                    audioTrackName = "Unknown " + "(" +
                                     attributes.AudioAttributes[i].bNumberOfChannels + "ch)";
                }
                if (attributes.AudioAttributes[i].LanguageExtension == DvdAudioLangExt.DirectorComments1)
                {
                    audioTrackName += " Director's Commentary";
                }
                else if (attributes.AudioAttributes[i].LanguageExtension == DvdAudioLangExt.DirectorComments2)
                {
                    audioTrackName += " Director's Commentary 2";
                }
                this.audioTracks.Add(i, audioTrackName);

            }


            this.subtitles.Add(-1, "Subtitles off");
            for (int i = 0; i < attributes.ulNumberOfSubpictureStreams; i++)
            {
                bool enabled;
                dvdInfo.IsSubpictureStreamEnabled(i, out enabled);
                if (!enabled)
                    continue;

                string subtitleName;
                try
                {
                    CultureInfo language = new CultureInfo(attributes.SubpictureAttributes[i].Language);
                    subtitleName = Regex.Replace(language.EnglishName, @"\(.*\)", "");
                }
                catch
                {
                    subtitleName = "Unknown";
                }
                this.subtitles.Add(i, subtitleName);
            }
            int subTitleStreams;
            dvdInfo.GetCurrentSubpicture(out subTitleStreams, out subtitle, out subtitlesDisabled);
            if (subtitlesDisabled)
                subtitle = -1;

            if (TitleChanged != null)
            {
                TitleChanged(this, null);
            }

            UpdateTrackAmount();

        }

        public bool SaveBookmark(string bookmarkName)
        {
            int hr;
            IDvdState pBookmark;

            hr = dvdInfo.GetState(out pBookmark);
            try
            {
                DsError.ThrowExceptionForHR(hr);
            }
            catch
            {
                return false;
            }

            // Next we serialize (persist) the data to the disc.
            // This always saves to the same bookmark bookmarkFile.  It could just as easily
            // save to a different one each time.
            //
            // We could also serialize the data to memory using IPersistMemory if we
            // don't want to persist across different executions of the program.
            IStorage pStorage;
            IStream pStream;
            STG_E e;
            hr = NativeMethods.StgOpenStorage(BookMarkFile, null, STGM.Write | STGM.ShareExclusive, IntPtr.Zero, 0, out pStorage);

            if (hr == 0)
            {
                e = (STG_E)pStorage.OpenStream(bookmarkName, IntPtr.Zero, STGM.ShareExclusive | STGM.Write, 0, out pStream);
                // we then open a stream object within that bookmarkFile

                if (e != 0)
                {
                    e = (STG_E)pStorage.CreateStream(bookmarkName, STGM.Create | STGM.Write | STGM.ShareExclusive, 0, 0, out pStream);
                    // Create a new stream so that old bookmarks can be overwritten
                    if (e != 0)
                    {
                        Marshal.ReleaseComObject(pBookmark);
                        Marshal.ReleaseComObject(pStorage);
                        return false;
                    }
                }
            }
            else
            {
                hr = NativeMethods.StgCreateDocfile(BookMarkFile, STGM.Create | STGM.Write | STGM.ShareExclusive, 0, out pStorage);
                if (hr != 0)
                {
                    Marshal.ReleaseComObject(pBookmark);
                    return false;
                }
                // we then create a stream object within that bookmarkFile
                hr = pStorage.CreateStream(bookmarkName, STGM.Write | STGM.ShareExclusive | STGM.Create, 0, 0, out pStream);
                if (hr != 0)
                {
                    Marshal.ReleaseComObject(pBookmark);
                    Marshal.ReleaseComObject(pStorage);
                    return false;
                }
            }
            // now we tell the bookmark to persist itself into the stream we just created
            try
            {
                IPersistStream pPersistStream = (IPersistStream)pBookmark;
                hr = NativeMethods.OleSaveToStream(pPersistStream, pStream);
                Marshal.ReleaseComObject(pPersistStream);
                Marshal.ReleaseComObject(pStorage);
                Marshal.ReleaseComObject(pStream);
                Marshal.ReleaseComObject(pBookmark);
                return true;
            }
            catch
            {
                Marshal.ReleaseComObject(pStorage);
                Marshal.ReleaseComObject(pStream);
                Marshal.ReleaseComObject(pBookmark);
                return false;
            }
        }

        public bool LoadBookmark(string bookmarkName)
        {

            // Begin by opening the bookmark bookmarkFile on the drive.
            int hr;
            IStorage pStorage;
            hr = NativeMethods.StgOpenStorage(BookMarkFile, null, STGM.Read | STGM.ShareExclusive,
                IntPtr.Zero, 0, out pStorage);
            if (hr != 0)
                return false;

            // open a stream object within that bookmarkFile
            IStream pStream;
            hr = pStorage.OpenStream(bookmarkName, IntPtr.Zero, STGM.Read | STGM.ShareExclusive, 0, out pStream);
            if (hr != 0)
            {
                Marshal.ReleaseComObject(pStorage);
                return false;
            }

            // load the bookmark in from the stream
            IDvdState pBookmark;
            object o;
            Guid g = new Guid("86303d6d-1c4a-4087-ab42-f711167048ef");
            hr = NativeMethods.OleLoadFromStream(pStream, ref g, out o);
            pBookmark = (IDvdState)o;
            Marshal.ReleaseComObject(pStorage);
            Marshal.ReleaseComObject(pStream);
            if (hr != 0)
                return false;

            // restore bookmark's state in the Navigator
            if (BeforeSendAsync != null)
                BeforeSendAsync(this, null); 
            IDvdCmd icmd;
            hr = dvdControl.SetState(pBookmark, DvdCmdFlags.Flush | DvdCmdFlags.None, out icmd);
            Marshal.ReleaseComObject(pBookmark);
            if (AfterSendAsync != null)
                AfterSendAsync(this, null);
            if (hr == 0)
                return true;
            else
                return false;

        }

        private enum STG_E : int
        {
            STG_E_ACCESSDENIED = (int)(0x80030005 - 0x100000000),
            STG_E_FILEALREADYEXISTS = (int)(0x80030050 - 0x100000000),
            STG_E_INSUFFICIENTMEMORY = (int)(0x80030008 - 0x100000000),
            STG_E_INVALIDFLAG = (int)(0x800300FF - 0x100000000),
            STG_E_INVALIDPOINTER = (int)(0x80030009 - 0x100000000),
            STG_E_INVALIDNAME = (int)(0x800300FC - 0x100000000),
            STG_E_INVALIDPARAMETER = (int)(0x80030057 - 0x100000000),
            STG_E_REVERTED = (int)(0x80030102 - 0x100000000),
            STG_E_TOOMANYOPENFILES = (int)(0x80030004 - 0x100000000)
        }


    }

}
